package com.winit.config.manager;

import com.winit.commons.util.SeqGenerate;
import com.winit.config.common.components.freemaker.TemplateFactory;
import com.winit.config.common.tools.CollectionUtil;
import com.winit.config.common.utils.TableIdUtil;
import com.winit.config.model.Config;
import com.winit.config.repository.ConfigRepositoryExt;
import com.winit.config.repository.IConfigRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Component
public class ConfigManager {

    private static Logger log = LoggerFactory.getLogger(ConfigManager.class);

    @Autowired
    private IConfigRepository<Config, String> configRepository;

    @Autowired
    private ConfigRepositoryExt ConfigRepositoryExt;

    @Autowired
    private TemplateFactory templateFactory;

    private final ThreadLocal<Set<String>> renderCycle = new ThreadLocal<>();

    private final ThreadLocal<Set<String>> mergeCycle = new ThreadLocal<>();

    public Config createConfig(Config config) {
        String configId = SeqGenerate.createId("CFG");
        config.setId(configId);
        configRepository.save(config);
        return config;
    }

    /**
     * 查出对应config_id已存在的config，（如果有）与传过来的config进行merge，然后保存
     *
     * @param update
     * @return
     */
    public Config updateConfigById(Config update) {
        return updateConfigById(update, true);
    }

    public Config updateConfigById(Config update, boolean isMerge) {
        Config existOne = this.configRepository.findOne(update.getId());
        if (existOne == null) {
            log.error("no config found for id:" + update.getId() + ", none thing will be done for this operation");
            return existOne;
        }
        if (isMerge) {
            this.mergeParamsToConfig(existOne, update.getKVMap(), update.getKRefMap(), update.getKChangeMap());
            //对于已删除的记录，只应该在该config的kChangeMap里找到记录，在该config的kvmap里是找不到记录的（防止垃圾数据）
            this.dealDeleteKvMap(existOne);
        } else {
            existOne = update;
        }
        this.ConfigRepositoryExt.updateExistPropertyById(existOne);
        return update;
    }

    /**
     * 将传过来的参数合并到config对应的参数里，config里的参数具有更高的优先级
     *
     * @param config
     * @param kvMap
     * @param kRefMap
     * @param kChangeMap
     */
    private void mergeParamsToConfig(Config config, Map<String, String> kvMap, Map<String, String> kRefMap, Map<String, String> kChangeMap) {
        if (!CollectionUtils.isEmpty(kvMap)) {
            if (null == config.getKVMap()) {
                config.setKVMap(new HashMap<String, String>());
            }
            CollectionUtil.mergeMap(config.getKVMap(), kvMap);
        }
        if (!CollectionUtils.isEmpty(kRefMap)) {
            if (null == config.getKRefMap()) {
                config.setKRefMap(new HashMap<String, String>());
            }
            CollectionUtil.mergeMap(config.getKRefMap(), kRefMap);
        }
        if (!CollectionUtils.isEmpty(kChangeMap)) {
            if (null == config.getKChangeMap()) {
                config.setKChangeMap(new HashMap<String, String>());
            }
            CollectionUtil.mergeMap(config.getKChangeMap(), kChangeMap);
        }
    }

    /**
     * @param id
     * @return 已经完成merge、render的config
     * @throws Exception mutual refer relation & mutual parent child relation
     */
    public Config getConfigDetailById(String id) throws Exception {
        return this.mergeRenderConfigById(id);
    }

    private Config mergeRenderConfigById(String id) throws Exception {
        Config config = this.mergeConfigById(id);
        if (null == config) {
            return null;
        }
        this.dealDeleteKvMap(config);
        this.renderConfig(config);
        return config;
    }

    private Config mergeConfigById(String id) throws Exception {
        Config config = this.configRepository.findOne(id);
        return this.mergeConfig(config);
    }

    private Config mergeConfig(Config config) throws Exception {
        Set<String> mergeCycleContainer = this.mergeCycle.get();
        if (null == mergeCycleContainer) {
            mergeCycleContainer = new HashSet<String>();
            this.mergeCycle.set(mergeCycleContainer);
        }
        String configId = config.getId();
        if (mergeCycleContainer.contains(configId)) {
            throw new Exception("mutual parent child relation found:" + mergeCycleContainer + "," + configId);
        }
        mergeCycleContainer.add(configId);
        String parentId = config.getParentId();
        if (!StringUtils.isEmpty(parentId)) {
            Config parentConfig = this.configRepository.findOne(parentId);
            if (null == parentConfig) {
                mergeCycleContainer.remove(configId);
                return config;
            }
            this.mergeParamsToConfig(parentConfig, config.getKVMap(), config.getKRefMap(), config.getKChangeMap());
            Config mergedConfig = this.mergeConfig(parentConfig);
            mergeCycleContainer.remove(configId);
            return mergedConfig;
        }
        mergeCycleContainer.remove(configId);
        return config;
    }

    private void renderConfig(Config config) throws Exception {
        String configId = config.getId();
        Set<String> renderCycleContainer = this.renderCycle.get();
        if (null == renderCycleContainer) {
            renderCycleContainer = new HashSet<String>();
            this.renderCycle.set(renderCycleContainer);
        }
        //防止相互引用死循环
        if (renderCycleContainer.contains(configId)) {
            throw new Exception("mutual reference err, configId:" + this.renderCycle.get() + "," + configId);
        }
        this.renderCycle.get().add(config.getId());
        Map<String, String> refMap = config.getKRefMap();
        Map<String, String> kvMap = config.getKVMap();
        if ((!CollectionUtils.isEmpty(refMap)) && (!CollectionUtils.isEmpty(kvMap))) {
            for (Map.Entry<String, String> entry : refMap.entrySet()) {
                String key = entry.getKey();
                if (kvMap.containsKey(key)) {
                    Config refConfig = this.getConfigDetailById(entry.getValue());
                    String renderValue = this.render(kvMap.get(key), refConfig.getKVMap());
                    kvMap.put(key, renderValue);
                }
            }
        }
        this.renderCycle.get().remove(configId);
    }

    private String render(String value, Map<String, String> models) throws IOException {
        return this.templateFactory.render(value, models);
    }

    private void dealDeleteKvMap(Config config) {
        Map<String, String> kChangeMap = config.getKChangeMap();
        if (!CollectionUtils.isEmpty(kChangeMap)) {
            Map<String, String> kvMap = config.getKVMap();
            for (Map.Entry<String, String> entry : kChangeMap.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if ("delete".equalsIgnoreCase(value)) {
                    kvMap.remove(key);
                }
            }
        }
    }

    /**
     * 将priorityId对应的config merge进destId对应的config中保存，将priorityId对应的config不变：
     * 1.如果对应destId的config_dest不存在，priority对应的config_pri存在，那么复制config_pri新建config_dest(id 为destId)；
     * 2.如果对应destId的config_dest存在,priority对应的config_pri不存在，不进行操作直接返回；
     * 3.如果对应destId的config_dest不存在,priority对应的config_pri不存在，不进行操作直接返回；
     * 4.如果两均存在，正常执行合并并保存操作
     * @param priorityId
     * @param destId
     * @param raw  是否先render, merge(sub-parent)再进行merge
     */
    public void mergeConfigById(String priorityId, String destId, boolean raw) throws Exception {
        Config priority = null;
        Config dest = null;
        if (raw){
            priority = this.configRepository.findOne(priorityId);
            dest = this.configRepository.findOne(destId);
        }else {
            priority = this.getConfigDetailById(priorityId);
            dest = this.getConfigDetailById(destId);
        }

        if (priority == null && dest == null){
            return;
        }
        if (priority == null){
            return;
        }
        if (dest == null){
            dest = priority;
            dest.setId(destId);
            this.configRepository.save(dest);
            return;
        }
        this.mergeParamsToConfig(dest, priority.getKVMap(), priority.getKRefMap(), priority.getKChangeMap());
        this.dealDeleteKvMap(dest);
        this.ConfigRepositoryExt.updateExistPropertyById(dest);
    }
}
